﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SpringFactory.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the SpringFactory type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Engine.Factory.Spring
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;

    using Collaboris.Saf.Engine.DynamicVariables;
    using Collaboris.Saf.Tracing;
    using Collaboris.Utils;

    using global::Spring.Context.Support;
    using global::Spring.Objects.Factory;
    using global::Spring.Objects.Factory.Config;

    using Utilities;

    /// <summary>
    /// SAF Factory object that uses Spring IOC container to dynamically create objects
    /// </summary>
    public class SpringFactory : ISafFactory
    {
        #region Constants and Fields

        /// <summary>
        /// alias resource.
        /// </summary>
        private static readonly string AliasResource = "file://" + Globals.AliasesFilePath;

        /// <summary>
        /// The macro name.
        /// </summary>
        /// <summary>
        /// macro object name.
        /// </summary>
        private static readonly string MacroName = "MacroToProcess";

        /// <summary>
        /// Sotes the name of the constructor parameter used to pass the collection of action names
        /// </summary>
        private static readonly string NamesParam = "actionNames";

        /// <summary>
        /// The config locations.
        /// </summary>
        private readonly List<string> configLocations;

        /// <summary>
        /// The saf context.
        /// </summary>
        private readonly SafContext safContext;

        /// <summary>
        /// Stores the instance of the spring context to be used by this factory
        /// </summary>
        private XmlApplicationContext springContext;

        /// <summary>
        /// The variables resolver.
        /// </summary>
        private DynamicVariableResolver variablesResolver =
            new DynamicVariableResolver(
                new SpringDynamicExpressonResolver(
                    DynamicVariablesPostProcessor.PlaceholderSubscribingPrefix, 
                    DynamicVariablesPostProcessor.PlaceholderSubscribingSuffix));

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SpringFactory"/> class.
        /// </summary>
        /// <param name="configLocation">
        /// The macro path.
        /// </param>
        /// <param name="safContext">
        /// The saf context.
        /// </param>
        public SpringFactory(string configLocation, SafContext safContext)
        {
            ValidationHelper.VerifyStringArgument(configLocation, "macroPath");
            ValidationHelper.VerifyObjectArgument(safContext, "safContext");

            this.configLocations = new List<string> { configLocation };
            this.safContext = safContext;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SpringFactory"/> class.
        /// </summary>
        /// <param name="configLocations">
        /// The config locations.
        /// </param>
        /// <param name="safContext">
        /// The saf context.
        /// </param>
        public SpringFactory(List<string> configLocations, SafContext safContext)
        {
            ValidationHelper.VerifyObjectArgument(configLocations, "configLocations");
            ValidationHelper.VerifyObjectArgument(safContext, "safContext");

            this.configLocations = configLocations;
            this.safContext = safContext;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the trace switch.
        /// </summary>
        /// <value>The trace switch.</value>
        public static TraceSwitch TraceSwitch
        {
            get
            {
                return SafTraceHelper.TraceSwitch;
            }
        }

        /// <summary>
        /// Stores the instance of the spring context to be used by this factory
        /// </summary>
        public XmlApplicationContext SpringContext
        {
            get
            {
                if (this.springContext == null)
                {
                    this.springContext = this.CreateXmlContext(this.configLocations, this.safContext);
                }

                return this.springContext;
            }
        }

        #endregion

        #region Public Methods

        /*
        /// <summary>
        /// The create macro.
        /// </summary>
        /// <param name="macroPath">
        /// The macro path.
        /// </param>
        /// <param name="safContext">
        /// The saf context.
        /// </param>
        /// <returns>
        /// </returns>
        public static IMacro CreateMacro(string macroPath, SafContext safContext)
        {
            ISafFactory factory = new SpringFactory(macroPath, safContext);

            return factory.CreateMacro();
        }
        */
        #endregion

        #region Implemented Interfaces

        #region ISafFactory

        /// <summary>
        /// The create action.
        /// </summary>
        /// <param name="actionName">
        /// The action name.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="NotImplementedException">
        /// </exception>
        public IAction CreateAction(string actionName)
        {
            // Change the definition to ensure that the action that we are creating has the same value as the action that we are requesting
            /*
            IObjectDefinition actionDefinition = this.SpringContext.GetObjectDefinition(actionName);
            if (actionDefinition == null)
            {
                throw new ObjectCreationException(
                    "Cannot dynamically create action because no object definition has been found", actionName);
            }
            */

            var action = (IAction)this.SpringContext.GetObject(actionName);
            if (action == null)
            {
                throw new ObjectCreationException(
                    "Error creating instace of action " + actionName, actionName);
            }

            // ensure name on the action corresponds to the name used by the factory to create the action
            //action.Name = actionName;

            return action;
        }

        /// <summary>
        /// Creates a new instance of a macro from a Spring XML file.
        /// The name of the macro on the xml file has to be "MacroToProcess"
        /// (as speficied by the MacroName constant)
        /// </summary>
        /// <returns>
        /// A new instance of a dynamic macro
        /// </returns>
        public IMacro CreateMacro()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SpringFactory", "CreateMacro");

            // We are creating a new macro so to ensure we start we a new clean set of objects
            // we need to delete the old context
            this.DeleteContext();

            /*
            // Change the definition to add the value of this factory to the contructor "factory" parameter
            IObjectDefinition macroDefinition = this.SpringContext.GetObjectDefinition(MacroName);
            if (macroDefinition == null)
            {
                throw new ObjectCreationException(
                    "Cannot dynamically create macro because no object definition has been found", MacroName);
            }

            // Check which constructor is been used. If we are passing the action names in the constructor then we need to add the 
            // factory to the constructor parameters so that the actions can be created.
            if (macroDefinition.ConstructorArgumentValues != null &&
                macroDefinition.ConstructorArgumentValues.ContainsNamedArgument(NamesParam))
            {
                macroDefinition.ConstructorArgumentValues.AddNamedArgumentValue("factory", this);
            }
            */

            // Get the macro
            Macro macro = (Macro)this.SpringContext.GetObject(MacroName);
            if (macro == null)
                throw new ObjectCreationException("Error creating Macro instance");

            macro.DynamicResolver = this.variablesResolver;
            macro.ActionFactory = this;
            
            /*
            SpringContext.
            var macro = (DynamicMacro)this.SpringContext.GetObject(MacroName,);
            macro.DynamicResolver = this.variablesResolver;
            */

            TraceHelper.TraceMethodEnd(TraceSwitch, "SpringFactory", "CreateMacro");

            return macro;
        }

        /// <summary>
        /// Deletes all instances of object currently store by the factory
        /// </summary>
        public void DeleteAllInstances()
        {
            this.DeleteContext();
        }

        #endregion

        #endregion

        #region Helper Methods

        /// <summary>
        /// Adds the place holder configurer.
        /// </summary>
        /// <param name="ctx">
        /// The CTX.
        /// </param>
        /// <returns>
        /// </returns>
        private static VariablePlaceholderConfigurer AddPlaceHolderConfigurer(SafContext ctx)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "AddPlaceHolderConfigurer");

            var placeholderConfigurer = new VariablePlaceholderConfigurer();
            placeholderConfigurer.IgnoreUnresolvablePlaceholders = true;
            placeholderConfigurer.VariableSources = GetConfigurerSources(ctx);

            TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "AddPlaceHolderConfigurer");

            return placeholderConfigurer;
        }

        /// <summary>
        /// Gets the configurer sources.
        /// </summary>
        /// <param name="ctx">
        /// The CTX.
        /// </param>
        /// <returns>
        /// </returns>
        private static ArrayList GetConfigurerSources(SafContext ctx)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "GetConfigurerSources");

            var listOfSources = new ArrayList();

            // Current Context.
            listOfSources.Add(ctx);

            // Config
            listOfSources.Add(new ConfigSectionVariableSource("appSettings"));

            // Environment Variables
            listOfSources.Add(new EnvironmentVariableSource());

            // Command Line Args
            listOfSources.Add(new CommandLineArgsVariableSource());

            // RegistryVariableSource
            listOfSources.Add(new RegistryVariableSource());

            TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "GetConfigurerSources");

            return listOfSources;
        }

        /// <summary>
        /// Creates the spring XML context.
        /// </summary>
        /// <param name="configLocations">
        /// The locations.
        /// </param>
        /// <param name="safContext">
        /// The o.
        /// </param>
        /// <returns>
        /// </returns>
        private XmlApplicationContext CreateXmlContext(List<string> configLocations, SafContext safContext)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SpringFactory", "CreateXmlContext");

            ValidationHelper.VerifyObjectArgument(configLocations, "configLocations");
            ValidationHelper.VerifyObjectArgument(safContext, "safContext");

            // Add file with aliases
            // configLocations.Add(AliasResource);

            // lets add in all the custom aliases from the punter.
            foreach (string aliasPath in ActionReflector.GetAliasesFromDir())
            {
                configLocations.Add(aliasPath);
            }

            // Create the xml context
            var context = new XmlApplicationContext(false, null, true, null, configLocations.ToArray());

            // Add the static placeholder post processor
            context.AddObjectFactoryPostProcessor(AddPlaceHolderConfigurer(safContext));

            // Add the dynamic variables post processor               
            context.AddObjectFactoryPostProcessor(
                new DynamicVariablesPostProcessor(
                    this.variablesResolver.AddObjectPublication, this.variablesResolver.AddObjectSubscription));
            
            context.Refresh();

            // Add the object instabce post processor
            context.ObjectFactory.AddObjectPostProcessor(
                new ObjectInstancesPostProcessor(context, this.variablesResolver.RegisterObjectInstance));

            TraceHelper.TraceMethodEnd(TraceSwitch, "SpringFactory", "CreateXmlContext");

            return context;
        }

        /// <summary>
        /// Deletes the context.
        /// </summary>
        private void DeleteContext()
        {
            if (this.springContext != null)
            {              
                this.springContext.Dispose();
                this.springContext = null;
            }
        }

        #endregion
    }
}